package xin.alum.aim.server;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.unix.Errors;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import xin.alum.aim.model.Aim;

import xin.alum.aim.socks.SocksServerInitializer;
import xin.alum.aim.websocks.WebSocketServerInitializer;

import java.util.List;

/**
 * 统一端口服务处理器[协议握手]
 * 注意：不能设置为Share
 * <p>
 * 使用同一个端口去处理TCP/HTTP协议的请求，因为HTTP的底层协议也是TCP，因此可以在此处理器内部可以通过解析部分数据
 * 来判断请求是TCP请求还是HTTP请求，然使用动态的pipeline切换
 *
 * @author Succy
 * create on 2020/11/19
 */
public class PortUnificationHandshake extends ByteToMessageDecoder {
    protected final InternalLogger logger = InternalLoggerFactory.getInstance(this.getClass());

    public PortUnificationHandshake() {
        super.setCumulator(ByteToMessageDecoder.COMPOSITE_CUMULATOR);
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
        // Will use the first five bytes to detect a protocol.
        if (in.readableBytes() < 4) {
            return;
        }
        final int magic1 = in.getUnsignedByte(in.readerIndex());
        final int magic2 = in.getUnsignedByte(in.readerIndex() + 1);

        ChannelPipeline p = ctx.pipeline();
        // 判断是不是HTTP请求
        if (isHttp(magic1, magic2)) {
            WebSocketServerInitializer.InitWebSocket(p);
        } else {//TCP协议握手成功
            //1、走YML配置约定协议，生产环境启用
            Aim.Agreement dataAgreement;
            //2、走TCP解码,协议握手
            in.markReaderIndex();
            in.readerIndex(in.readableBytes() >= 5 ? 1 : 0);
            int agreement = in.readInt();
            //直接发送协议
            if (agreement < 5 && xin.alum.aim.Aim.request.onHandShake(ctx.channel(), in)) {
                dataAgreement = Aim.Agreement.forNumber(agreement);
            } else { //ProtoBuf Login登录认证
                in.resetReaderIndex();
                dataAgreement = Aim.Agreement.ProtoBuf;
            }
            //3、初始化通道协议
            SocksServerInitializer.InitSocket(p, dataAgreement);
            //4、传递in消息
            ctx.fireChannelRead(in.retain());
        }
        // 将自身移除掉
        p.remove(this);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        if (!(cause instanceof Errors.NativeIoException)) {
            logger.error(cause);
        }
    }

    /**
     * 判断请求是否是HTTP请求
     *
     * @param magic1 报文第一个字节
     * @param magic2 报文第二个字节
     * @return
     */
    private boolean isHttp(int magic1, int magic2) {
        return magic1 == 'G' && magic2 == 'E' || // GET
                magic1 == 'P' && magic2 == 'O' || // POST
                magic1 == 'P' && magic2 == 'U' || // PUT
                magic1 == 'H' && magic2 == 'E' || // HEAD
                magic1 == 'O' && magic2 == 'P' || // OPTIONS
                magic1 == 'P' && magic2 == 'A' || // PATCH
                magic1 == 'D' && magic2 == 'E' || // DELETE
                magic1 == 'T' && magic2 == 'R' || // TRACE
                magic1 == 'C' && magic2 == 'O';   // CONNECT
    }

}
